#!/usr/bin/env python
# coding=utf-8


from enum import IntEnum, auto

from PySide6.QtCore import QMutex, QMutexLocker, QPropertyAnimation, Qt
from PySide6.QtGui import QMouseEvent
from PySide6.QtWidgets import (
    QGraphicsOpacityEffect,
    QGridLayout,
    QMainWindow,
    QMessageBox,
    QPushButton,
)

from ui.game_ui import Ui_MainWindow
from widgets.block import BlockWidget
from widgets.mask import MaskWidget

from .parser import GameParser, GameSolution
from .signal import SignalButton, reset_button_effect


class StatEnum(IntEnum):
    Default = auto()
    Edit = auto()
    Guess = auto()


class MainWindow(
    QMainWindow,
    Ui_MainWindow,
):
    def __init__(self):
        self.reset_button = SignalButton()
        super().__init__()
        self.setupUi(self)
        self.lock = QMutex()
        self.stat: StatEnum = None
        self.game_parse = GameParser()
        self.game_solutions: GameSolution = None
        self.__init_memu()
        self.__init_board()
        self.__init_mask()

    def __init_memu(self):
        self.menuExit.triggered.connect(self.close)
        self.menuReset.triggered.connect(self.menu_reset)
        self.menuGuess.triggered.connect(self.menu_guess)
        self.menuSolve.triggered.connect(self.menu_solve)
        self.menuEdit.triggered.connect(self.menu_edit)
        self.menuHelp.triggered.connect(self.menu_help)

    def __init_board(self):
        self.blocks = [BlockWidget(layout, self.reset_button) for layout in self.layoutWidget.findChildren(QGridLayout)]
        col = self.boardLayout.columnCount()
        row = self.boardLayout.rowCount()
        self.board = [[0] * col for _ in range(row)]
        self._board: list[tuple[int, int]] = []
        for i in range(row):
            for j in range(col):
                btn: QPushButton = self.boardLayout.itemAtPosition(i, j).widget()
                effect = QGraphicsOpacityEffect(btn)
                btn.setGraphicsEffect(effect)
                btn.mousePressEvent = self.wrapperButtonMousePressEvent(btn, effect, i, j)
                self.reset_button.connect_slot(btn, reset_button_effect, effect)

    def __init_mask(self):
        self.mask_window = MaskWidget(self.centralwidget)
        self.mask_blocks = MaskWidget(self.layoutWidget)

    def wrapperButtonMousePressEvent(self, btn: QPushButton, effect: QGraphicsOpacityEffect, row: int, col: int):
        _mouseEvent = btn.mouseMoveEvent

        def wrapper(event: QMouseEvent):
            match event.button():
                case Qt.MouseButton.RightButton:
                    if self.board[row][col] == -1:
                        return
                    if self.stat != StatEnum.Guess:
                        self.board[row][col] ^= 1
                        if btn.text() == "":
                            btn.setText("●")
                            if (row, col) not in self._board:
                                self._board.append((row, col))
                        else:
                            if (row, col) in self._board:
                                self._board.remove((row, col))
                            btn.setText("")
                        return
                    if self.board[row][col] != 1:
                        btn.setText("●")
                        if (row, col) not in self._board:
                            self._board.append((row, col))
                        self.board[row][col] = 1
                        self.game_solutions.change(row, col, 1)
                case Qt.MouseButton.LeftButton:
                    if self.board[row][col] == 1:
                        return
                    if self.stat != StatEnum.Guess:
                        effect.setOpacity(0.7 if effect.opacity() == 0 else 0)
                        self.board[row][col] ^= -1
                        return
                    if self.board[row][col] != -1:
                        effect.setOpacity(0)
                        self.board[row][col] = -1
                        self.game_solutions.change(row, col, -1)
                case _:
                    _mouseEvent(event)

        return wrapper

    def __reset_board(self):
        for i in range(len(self.board)):
            self.board[i] = [0] * len(self.board[i])
        self._board.clear()

    def __reset_blocks(self):
        for block in self.blocks:
            block.reset()

    def menu_reset(self):
        with QMutexLocker(self.lock):
            if self.stat == StatEnum.Default:
                return
            self.mask_window.show_mask()
            self.reset_button.emit_signal()
            self.__reset_board()
            self.__reset_blocks()
            self.game_solutions = None
            self.mask_window.hide_mask()
            self.mask_blocks.hide_mask()
            self.stat = StatEnum.Default

    def menu_edit(self):
        with QMutexLocker(self.lock):
            if self.stat == StatEnum.Edit:
                return
            self.stat = StatEnum.Edit
            self.mask_blocks.hide_mask()
            self.mask_window.hide_mask()

    def menu_solve(self):
        try:
            self.lock.lock()
            _stat = False
            if (
                QMessageBox(
                    QMessageBox.Icon.Warning,
                    "info",
                    "Are you sure to sovle puzzle?",
                    parent=self,
                    standardButtons=QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Cancel,
                ).exec()
                != QMessageBox.StandardButton.Ok
            ):
                _stat = True
                return
            if self.stat != StatEnum.Guess:
                self.mask_blocks.show_mask()
                self.mask_window.show_mask()
                self.game_solutions = self.game_parse.solve(self.board, blocks=[block.block for block in self.blocks])
                for row, col in self._board:
                    self.game_solutions.change(row, col, 1)
                self.stat = StatEnum.Guess
                self.mask_window.hide_mask()
                _stat = True
        except Exception as e:
            self.mask_blocks.hide_mask()
            QMessageBox(
                QMessageBox.Icon.Warning,
                "warning",
                f"Guess failed, {e.args[0]}",
                parent=self,
                standardButtons=QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Cancel,
            ).exec()
        finally:
            if not _stat:
                self.mask_window.hide_mask()
            self.lock.unlock()

    def _guess_action(self, btn: QPushButton):
        """
        QPropertyAnimation
        Parent must be specified, otherwise it will be garbage collected
        """
        if getattr(btn, "_animation_runing", False):
            return
        else:
            setattr(btn, "_animation_runing", True)
        effect: QGraphicsOpacityEffect = btn.graphicsEffect()
        opacity = effect.opacity()
        animation = QPropertyAnimation(effect, b"opacity", btn)
        animation.setDuration(800)
        animation.setKeyValues((
            (0, opacity),
            (0.5, 0),
            (1.0, opacity),
        ))
        animation.setLoopCount(3)
        animation.finished.connect(lambda: setattr(btn, "_animation_runing", False))
        animation.start()

    def menu_guess(self):
        with QMutexLocker(self.lock):
            self.mask_window.show_mask()
            text = None
            if self.stat != StatEnum.Guess or self.game_solutions is None:
                text = "The puzzle has not been confirmed, please set the puzzle first"
            elif (guess := self.game_solutions.guess()) is None:
                text = "Guess fails, the puzzle is unsolved, or it has been completed"
            if text is not None:
                QMessageBox(
                    QMessageBox.Icon.Warning,
                    "warning",
                    text,
                    parent=self,
                    standardButtons=QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Cancel,
                ).exec()
            else:
                self._guess_action(self.boardLayout.itemAtPosition(guess[0], guess[1]).widget())
            self.mask_window.hide_mask()

    def menu_help(self):
        with QMutexLocker(self.lock):
            QMessageBox(
                QMessageBox.Icon.Information,
                "help",
                """上面是block, 下面是board
block左键标记, board左键隐藏, 右键标记
solve后无法修改block, 修改board后无法返回
可以切换编辑模式修改后再次solve""",
                parent=self,
                standardButtons=QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Cancel,
            ).exec()
